Go 垃圾回收:简单理解版
因为总也记不住, 必须从笔记当中挪出来放到博客中. 为了面子, 必须记住.
Go 的垃圾回收(GC)可以先简单理解为两个步骤:
标记存活对象 → 清除垃圾对象
这种方式叫作 标记—清除(Mark-Sweep)。
第二阶段没什么知识点,就是把白色对象从堆中删除。
三色标记法
三色标记法描述的是在第一阶段,也就是标记存活对象时使用的方法。
- 灰色代表还没有完成扫描;
- 黑色代表扫描已经完成。
三色标记过程
- 从 root 开始,找到所有可达对象,并标记为灰色。root 指:
- 全局变量;
- goroutine 栈中的变量;
- 扫描灰色对象,将它们标记为黑色,并将它们引用的白色对象标记为灰色;
- 重复步骤 2,直到没有灰色对象为止。
一次完整的标记过程
- 短暂 STW;
- 标记和业务代码并发运行;
- 短暂 STW。
STW
什么是 STW?
STW 是 Stop The World 的缩写,意思是暂停所有业务 goroutine 的运行,让 GC 独占 CPU 资源。
STW 都做了什么?
- 第一次 STW
- 暂停业务 goroutine;
- 统一开启写屏障;
- 恢复业务 goroutine。
- 第二次 STW
- 暂停业务 goroutine;
- 关闭写屏障;
- 最后清理一次写屏障引入的灰色节点;
- 恢复业务 goroutine。
为什么需要 STW?
保证所有业务 goroutine 都在同一个 GC 状态下运行,避免漏标存活对象。
为什么要使用三色标记?
三色标记法存在的意义是,能够让 GC 和业务代码同时运行。
- 如果一次扫描整个堆,并且扫描期间完全停止业务,那么堆越大,业务暂停的时间就可能越长。
- 三色标记可以满足让 GC 和业务代码并发运行的需求。
- GC 扫一半,让渡 CPU 资源给业务 goroutine,回来的时候,继续扫描剩下的灰色对象,黑色就不扫了;
- 但是业务 goroutine 可能会修改对象之间的引用关系,譬如:
A --> B,然后 goroutine 切换;A --> C,这时候,B 是垃圾,C 不是了。这个时候,引入写屏障解决。
写屏障
写屏障可以简单理解为:业务代码修改指针引用时,由编译器插入的一小段辅助逻辑,用来通知 GC 对象引用关系发生了变化,避免并发标记过程中漏标存活对象。
常见的写屏障有两种:
插入写屏障
- 关注新写入的指针;
- 例如将
A --> B修改为A --> C时,把新指向的对象C标记为灰色; - 这样即使
A已经扫描完成,GC 仍然会继续扫描C。
删除写屏障
- 关注被覆盖、即将删除的旧指针;
- 将
A --> B修改为A --> C时,把旧指针指向的对象B标记为灰色; - 即使
B刚刚失去引用,本轮 GC 也暂时保留它,避免 GC 与业务代码并发修改引用时发生漏标。
删除写屏障的目的是什么?解决了什么场景的问题?
- B 是白色;
- 有 goroutine 引入了 B;
- B 真的变成白色了,这是不行的。
删除写屏障带来的问题是,垃圾回收不干净。譬如上面的 B 也许没有被引用,真的应该被回收。不过也没关系,因为下一轮 GC 会把它回收掉。
<完>